#pragma once
#include <elasticlient/client.h>
#include <elasticlient/scroll.h>
#include <cpr/cpr.h>
#include <json/json.h>
#include "log.hpp"

#include <sstream>

bool Serialize(const Json::Value& src, std::string& out) {
    Json::StreamWriterBuilder writer;
    std::unique_ptr<Json::StreamWriter> jw (writer.newStreamWriter());
    std::stringstream ss;
    int err = jw->write(src, &ss);
    if(err) {
        LOG_ERROR("序列化失败 ");
        out = src.toStyledString();
        return false;
    }
    out = ss.str();
    return true;
}



bool UnSerialize(const std::string& src, Json::Value& out) {
    Json::CharReaderBuilder writer;
    std::unique_ptr<Json::CharReader> jr(writer.newCharReader());
    std::string errs;
    jr->parse(src.c_str(), src.c_str() + src.size(), &out, &errs);
    if(!errs.empty()) {
        LOG_ERROR("反序列化失败 ");
        return false;
    }
    return true;
}


//向ES新增一个索引
class ESIndex {
public:
    ESIndex(std::shared_ptr<elasticlient::Client> client, const std::string& name, const std::string& type = "_doc")
        :_client(client)
        , _name(name)
        , _type(type) {
        //构建请求体中的settings
        Json::Value settings;
        Json::Value analysis;
        Json::Value analyzer;
        Json::Value ik;
        ik["tokenizer"] = "ik_max_word";
        analyzer["ik"] = ik;
        analysis["analyzer"] = analyzer;
        settings["analysis"] = analysis;
        _index_body["settings"] = settings;
    }


    //构建请求体的properties
    ESIndex& append(const std::string& key,
                   const std::string& type = "text",
                   const std::string& analyzer = "ik_max_word",
                   bool enabled = true) {
        Json::Value field;
        field["type"] = type;
        field["analyzer"] = analyzer;
        std::string key_name = key; 
        _protperties[key_name] = field;
        if(!enabled) {
            _protperties[key_name]["enabled"] = false;
        }
        return *this;
    }

    bool indexExist(const std::string& index) {
        cpr::Response resp;
        try {
            resp = _client->performRequest(elasticlient::Client::HTTPMethod::GET, index, "");
        } catch(const elasticlient::ConnectionException& e) {
            LOG_ERROR("向ES查询索引 {} 是否存在失败, {}", index, e.what());
            return false;
        } 
        if(resp.status_code >= 200 && resp.status_code < 300) {
            LOG_DEBUG("索引 {} 已存在", index);
            return true;
        } 
        return false;
    }
    //创建请求体
    bool create(const std::string& index_id = "default_index_id") {
        if(indexExist(_name)) {
            LOG_INFO("索引 {} 已存在, 无需创建", _name);
            return true;
        }
        Json::Value mappings;
        mappings["dynamic"] = true;
        mappings["properties"] = _protperties;
        _index_body["mappings"] = mappings;
        cpr::Response resp;
        try {
            resp = _client->index(_name, _type, index_id, _index_body.toStyledString());
            if(resp.status_code < 200 || resp.status_code >= 300) {
                LOG_ERROR("{}", resp.status_code);
                LOG_ERROR("创建索引 {} 失败, {}", _name, resp.error.message);
                LOG_ERROR("{}", _index_body.toStyledString());
                return false;
            }
        }
        catch(const elasticlient::ConnectionException& e) {
            LOG_ERROR("创建索引 {} 失败: {}", _name, e.what());
            return false;
        }
        LOG_DEBUG("{}", resp.text);
        return true;
    }
private:
    std::shared_ptr<elasticlient::Client> _client;
    std::string _name;
    std::string _type;
    Json::Value _index_body;
    Json::Value _protperties;
};


class ESInsert {
public:
    ESInsert(std::shared_ptr<elasticlient::Client> client, const std::string& name, const std::string& type = "_doc")
        :_client(client)
        , _name(name)
        , _type(type) {}


    template<typename T>
    ESInsert& append(const std::string& key, const T& value) {
        _insert_body[key] = value;
        return *this;
    }


    bool insert(const std::string& id = "") {
        cpr::Response resp;
        try {
            resp = _client->index(_name, _type, id, _insert_body.toStyledString());
            if(resp.status_code < 200 || resp.status_code >= 300) {
                LOG_ERROR("新增数据 {} 失败, {}", _name, resp.error.message);
                return false;
            }
        }
        catch(const elasticlient::ConnectionException& e) {
            LOG_ERROR("新增数据 {} 失败: {}", _name, e.what());
            return false;
        }
        // LOG_DEBUG("{}", _insert_body.toStyledString());
        // LOG_DEBUG("{}", resp.text);
        return true;

    }

private:
    std::shared_ptr<elasticlient::Client> _client;
    std::string _name;
    std::string _type;
    Json::Value _insert_body;
};


class ESRemove {
public:
    ESRemove(std::shared_ptr<elasticlient::Client> client, const std::string& name, const std::string& type = "_doc")
        :_client(client)
        , _name(name)
        , _type(type) {}
    bool remove(const std::string& id) {
        cpr::Response resp;
        try {
            resp = _client->remove(_name, _type, id);
            if(resp.status_code < 200 || resp.status_code >= 300) {
                LOG_ERROR("删除数据 {} 失败, {}", _name, resp.error.message);
                return false;
            }
        }
        catch(const elasticlient::ConnectionException& e) {
            LOG_ERROR("删除数据 {} 失败: {}", _name, e.what());
            return false;
        }
        // LOG_DEBUG("{}", resp.text);
        return true;
    }
private:
    std::shared_ptr<elasticlient::Client> _client;
    std::string _name;
    std::string _type;
};



class ESSearch {
public:
    ESSearch(std::shared_ptr<elasticlient::Client> client, const std::string& name, const std::string& type = "_doc")
        :_client(client)
        , _name(name)
        , _type(type) {}


    ESSearch& append_must_not_terms(const std::string& key, const std::vector<std::string>& vals) {
        Json::Value field;
        for(const auto& val : vals) {
            field[key].append(val);
        }
        Json::Value terms;
        terms["terms"] = field;
        _must_not.append(terms);
        return *this;
    }


    ESSearch& append_should_match(const std::string& key, const std::string& value) {
        Json::Value field;
        field[key] = value;
        Json::Value match;
        match["match"] = field;
        _should.append(match);
        return *this;
    }


    ESSearch& append_must_term(const std::string& key, const std::string& value) {
        Json::Value field;
        field[key] = value;
        Json::Value term;
        term["term"] = field;
        _must.append(term);
        return *this;
    }


    ESSearch& append_must_match(const std::string& key, const std::string& value) {
        Json::Value field;
        field[key] = value;
        Json::Value match;
        match["match"] = field;
        _must.append(match);
        return *this;

    }


    Json::Value search() {
        cpr::Response resp;
        Json::Value cond;
        if(_must_not)
            cond["must_not"] = _must_not;
        if(_should)
            cond["should"] = _should;
        if(_must)
            cond["must"] = _must;
        if(cond.empty()) {
            try {
                resp = _client->search(_name, _type, "");
                if(resp.status_code < 200 || resp.status_code >= 300) {
                    LOG_ERROR("查询数据 {} 失败, {}", _name, resp.error.message);
                    return Json::Value();
                }
            }
            catch(const elasticlient::ConnectionException& e) {
                LOG_ERROR("查询数据 {} 失败: {}", _name, e.what());
                return Json::Value();
            }
        }
        else {
            _search_body["query"]["bool"] = cond;
            try {
                LOG_DEBUG("{}", _search_body.toStyledString());
                resp = _client->search(_name, _type, _search_body.toStyledString());

                if(resp.status_code < 200 || resp.status_code >= 300) {
                    LOG_ERROR("查询数据 {} 失败, {}", _name, resp.error.message);
                    return Json::Value();
                }
            }
            catch(const elasticlient::ConnectionException& e) {
                LOG_ERROR("查询数据 {} 失败: {}", _name, e.what());
                return Json::Value();
            }

        }
        // LOG_DEBUG("{}", _search_body.toStyledString());
        // LOG_DEBUG("检索响应正文: [{}]", resp.text);
        Json::Value ret;
        bool ok = UnSerialize(resp.text, ret);
        if(!ok) {
            LOG_ERROR("反序列化查询结果失败: {}", resp.text);
            return Json::Value();
        }
        // std::string tmp;
        // Serialize(ret, tmp);
        // LOG_DEBUG("反序列化查询结果: [{}]", tmp);
        return ret["hits"]["hits"];
    }
private:
    std::shared_ptr<elasticlient::Client> _client;
    std::string _name;
    std::string _type;
    Json::Value _search_body;
    Json::Value _must_not;
    Json::Value _must;
    Json::Value _should;
};